/*
//  JTFileData.m
//  Agate Rule Tester
//
//  Created by Jim Turner on 11/20/05.

License: GNU General Public License
This program is free software; you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation; either version 2 of the License, or (at your option) any later 
version.

This program is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 
details.

You should have received a copy of the GNU General Public License along 
with this program; if not, write to the Free Software Foundation, Inc., 
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
http://www.gnu.org/licenses/licenses.html#GPL

*/

#import "JTFileData.h"
#import "JTRule.h"

@implementation JTFileData

#pragma mark Object Initialization and Destruction
+(id)initWithPath:(NSString *)path
{
	return( [[self alloc] doInit:path] );
}

-(id)doInit:(NSString *)path
{
	if( self = [super init] );
	{
		fileDataPath = [NSString stringWithString:path];
		[fileDataPath retain];

		fileData = [NSData dataWithContentsOfFile:fileDataPath];
		[fileData retain];

		openedAsASCII = NO;
		
		/* It's important to note that both the controller and JTFileData have their
		   own perl interpreter objects.  They are not sharing one as the controller
		   needs the ability to test regex's as they are input which can happen before
		   a JTFileData object is instantiated.
		*/
		perl = [[JTPerlInterface alloc] init];
		
		/* We want just the text from [STX] to [ETX].  Anything before or after will be 
		   handled by WireSpeed anyways. */
		extractionError = nil;
		NSString *tmpStr = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding];
		if( tmpStr == nil )
		{
			// Ok, the file didn't open up as a valid UTF8 file.  Try to salvage this mess and open
			// it as an ASCII file.
			tmpStr = [[NSString alloc] initWithData:fileData encoding:NSASCIIStringEncoding];
			if( tmpStr == nil )
			{
				extractionError = [NSString stringWithString:@"Woah... two nil pointers returned from [NSString initWithData:encoding:]\n\n"
															 @"This file is unreadable by the agate tester, I'm sorry.  There's nothing I can do."];
				[extractionError retain];
			
				return( self );
			}
			else
			{
				// Set the bit that'll tell the controller we opened this file in a not-so-friendly mode
				openedAsASCII = YES;
				extractedStoryText = [perl extractStoryText:tmpStr];
			}
		}
		else
		{
			extractedStoryText = [perl extractStoryText:tmpStr];
		}

		[extractedStoryText retain];
		
		if( [perl wasLastExtractionSuccessful] == NO )
		{
			extractionError = [NSString stringWithString:[perl extractionError]];
			[extractionError retain];
		}
	}

	return( self );
}

-(void) dealloc
{
	[fileDataPath release];
	[fileData release];
	[extractedStoryText release];
	if( extractionError != nil )
	{
		[extractionError release];
	}
	
	[perl release];

	[super dealloc];
}

-(NSString *)getExtractionError
{
	NSLog( @"returning [%@]", extractionError );

	return( extractionError );
}

#pragma mark Perl-related stuff
-(NSString *)runRules:(NSMutableArray *)rules afterBaseRules:(NSMutableArray *)baseRules
{	
	// We always start off with a brand-new copy of the agate text
	currentAgateString = [self originalFileData];

	// Allocate some space to begintext and endtext holders
	beginTextRules = [NSMutableArray new];
	endTextRules   = [NSMutableArray new];

	// Run base rules
	[self processRuleSet:baseRules ignoreBeginEndCheck:NO];
	
	// Run the main translation
	[self processRuleSet:rules ignoreBeginEndCheck:NO];
	
	// Run any #begintext and #endtext rules
	[self processRuleSet:beginTextRules ignoreBeginEndCheck:YES];
	[self processRuleSet:endTextRules ignoreBeginEndCheck:YES];
	
	// Once all the processing of rules (if any) are complete, check to
	// see if we need to show hidden characters or not.
	if( showDTMnemonics == YES )
	{
		NSLog( @"showing DT mnemonics" );
	
		// call the perl routine that shows all the special charaters
		currentAgateString = [perl showDTMnemonics:currentAgateString];
	}
	
	/* release the begin and endTextRules objects */
	[beginTextRules release];
	[endTextRules release];
	
	return( currentAgateString );
}

-(void)processRuleSet:(NSMutableArray *)ruleset ignoreBeginEndCheck:(BOOL)ignoreCheck
{
	NSEnumerator   *ruleEnum;
	id obj;

	ruleEnum = [ruleset objectEnumerator];
	while( obj = [ruleEnum nextObject] )
	{
		JTRule *theRule = [obj objectForKey:@"theRule"];
	
		// If activeState is not 1, we ignore this rule
		if( [theRule activeState] == 0 )
		{
			NSLog( @"Ignoring inactive rule" );
			continue;
		}
	
		/* 12.01.05 - jturner - If the lookfor is [#begintext], we need to hold off processing
		                        those rules until the very end as things like "replace ; with [TAB]"
								would destroy a TabRule in the InDesign definition.
								We place each rule as we find them in reverse order so that if there
								are 3 rules, for example, rule 3 is processed first, then rule 2 would
								go on top of 3, then 1 on top of 2 (exactly how the rules look in the
								app's list).
								
		   12.02.05 - jturner - Same thing goes for [#endtext], except that these rules go in order that
								they are displayed in the list, not in reverse.
		*/
		if( !ignoreCheck )
		{
			if( [[[theRule find] uppercaseString] isEqualTo:@"[#BEGINTEXT]"] )
			{
				NSLog( @"Rule is a [#begintext] replacement... saving for later" );
		
				[beginTextRules insertObject:obj atIndex:0];
				continue;
			}

			if( [[[theRule find] uppercaseString] isEqualTo:@"[#ENDTEXT]"] )
			{
				NSLog( @"Rule is a [#endtext] replacement... saving for later" );
		
				[endTextRules addObject:obj];
				continue;
			}
		}
		
		// Now we get to make some decisions... firstly, is this a regex?
		// We compare found ranges to see if [#perl] in there anyplace.
		if( ([theRule isRegex] == YES) || (NSEqualRanges( [[theRule find] rangeOfString:@"[#perl]" options:NSAnchoredSearch], NSMakeRange( 0, 7 ) )) )
		{
			NSMutableString *find    = [NSMutableString stringWithString:[theRule find]];
			NSMutableString *replace = [NSMutableString stringWithString:[theRule replace]];
		
			// Trim [#perl] if we need to
			if( (NSEqualRanges( [find rangeOfString:@"[#perl]" options:NSAnchoredSearch], NSMakeRange( 0, 7 ) )) )
			{
				[find setString:[find substringWithRange:NSMakeRange( 7, [find length] - 7 )]];
			}

			if( (NSEqualRanges( [replace rangeOfString:@"[#perl]" options:NSAnchoredSearch], NSMakeRange( 0, 7 ) )) )
			{
				[replace setString:[replace substringWithRange:NSMakeRange( 7, [replace length] - 7 )]];
			}
		
			// It is.  Is it a two parter?  Did they put a m// or // in the find section and a s/// in the
			// replace?  Seems silly to do it this way, but WireSpeed supports it.
			if( ![replace isEqualTo:@""] )
			{
				// It's a two parter.
				// We'll first see if the find part finds anything.  If it does, we'll simply send in
				// the replace part right after it.
				int matchesFound = [perl matchRegex:find onText:currentAgateString];
				if( matchesFound > 0 )
				{
					currentAgateString = [perl applyRegex:replace onText:currentAgateString];
				}
			}
			else
			{
				// The entire regex is in 'find'
				currentAgateString = [perl applyRegex:find onText:currentAgateString];
			}
		}
		else
		{
			// Not a regex.  All the other options can apply now.
			// We send everything to the perl object as it is better equipped to make decisions
			// (and fix things that those decisions will require)
			currentAgateString = [perl applyFind:[theRule find] 
									  andReplace:[theRule replace] 
										  onText:currentAgateString
								   caseSensitive:[theRule isCaseSensitive]
								    replaceFirst:[theRule isReplaceFirstOnly]
									 replaceLast:[theRule isReplaceLastOnly]
									  exhaustive:[theRule isExhaustiveReplace]];
		}
	}
}

#pragma mark Accessor methods
-(NSString *)originalFileData
{
	return( extractedStoryText );
}

- (BOOL) openedAsASCII
{
	return( openedAsASCII );
}

- (void) toggleDTMnemonics:(BOOL)state
{
	showDTMnemonics = state;
}

@end
